﻿/* *
 * =============================================================================
 * File Name:               DFlipFlop.cs
 * Programmer:              Ken Watts
 * Creation Date:           8/3/2008
 * =============================================================================
 * Description:             Class that represents a D-type flip-flop.
 * =============================================================================
 * Modification Date:       8/3/2008 - Initial Revision
 * =============================================================================
 * */

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DigitalLogicSimulator
{
    public class DFlipFlop : Device
    {
        // Constants
        private const int k_int_num_inputs = 3;
        private const int k_int_input_x_offset = 2;
        private const int k_int_input_y_offset = 1;
        private const int k_int_d_input = 0;
        private const int k_int_clk_input = 1;
        private const double k_initial_top = 210;
        private const double k_initial_left = 70;
        // Perimeter points
        private const double k_p1_x = 35;
        private const double k_p1_y = 40;
        private const double k_p2_x = 10;
        private const double k_p2_y = 40;
        private const double k_p3_x = 10;
        private const double k_p3_y = 0;
        private const double k_p4_x = 35;
        private const double k_p4_y = 0;
        // Inputs and outputs
        private const double k_in_d_p1_x = 0;
        private const double k_in_d_p1_y = 10;
        private const double k_in_d_p2_x = 10;
        private const double k_in_d_p2_y = 10;
        private const double k_in_clk_p1_x = 0;
        private const double k_in_clk_p1_y = 30;
        private const double k_in_clk_p2_x = 10;
        private const double k_in_clk_p2_y = 30;
        private const double k_out_q_p1_x = 35;
        private const double k_out_q_p1_y = 10;
        private const double k_out_q_p2_x = 45;
        private const double k_out_q_p2_y = 10;
        private const double k_out_q_not_p1_x = 35;
        private const double k_out_q_not_p1_y = 30;
        private const double k_out_q_not_p2_x = 45;
        private const double k_out_q_not_p2_y = 30;
        // Letters
        private const double k_d_p1_x = 13;
        private const double k_d_p1_y = 4.75;
        private const double k_d_p2_x = 13;
        private const double k_d_p2_y = 13.5;
        private const double k_d_p3_x = 14;
        private const double k_d_p3_y = 5.5;
        private const double k_d_radius = 3.75;
        private const double k_d_rotational_angle = 180;
        private const double k_clk_p1_x = 10;
        private const double k_clk_p1_y = 27;
        private const double k_clk_p2_x = 16;
        private const double k_clk_p2_y = 30.5;
        private const double k_clk_p3_x = 10;
        private const double k_clk_p3_y = 33;
        private const double k_clk_p4_x = 16;
        private const double k_clk_p4_y = 29.5;
        private const double k_q_center_x = 28;
        private const double k_q_center_y = 9;
        private const double k_q_radius = 4;
        private const double k_q_p1_x = 28.5;
        private const double k_q_p1_y = 9.5;
        private const double k_q_p2_x = 32.5;
        private const double k_q_p2_y = 13.5;
        private const double k_q_not_center_x = 28;
        private const double k_q_not_center_y = 30;
        private const double k_q_not_radius = 4;
        private const double k_q_not_p1_x = 28.5;
        private const double k_q_not_p1_y = 30.5;
        private const double k_q_not_p2_x = 32.5;
        private const double k_q_not_p2_y = 34.5;
        private const double k_q_not_p3_x = 23.5;
        private const double k_q_not_p3_y = 23.5;
        private const double k_q_not_p4_x = 32.5;
        private const double k_q_not_p4_y = 23.5;

        // Input states
        bool inputDState = false, inputClkState = false;

        public DFlipFlop()
        {
            // Create an array for the input devices
            inputDevices = new Device[k_int_num_inputs];            
        }

        protected override void ReplaceInstance(Panel parentPanel)
        {
            parentPanel.Children.Add(new DFlipFlop());
        }

        protected override void Draw()
        {
            pathGeometry = new PathGeometry();
            pathGeometry.FillRule = FillRule.Nonzero;
            PathFigure pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_p1_x, k_p1_y);
            pathFigure.IsClosed = true;
            pathGeometry.Figures = new PathFigureCollection();
            pathGeometry.Figures.Add(pathFigure);
            pathFigure.Segments = new PathSegmentCollection();

            // Draw outline 
            LineSegment ls = new LineSegment();
            ls.Point = new Point(k_p2_x, k_p2_y);
            pathFigure.Segments.Add(ls);
            ls = new LineSegment();
            ls.Point = new Point(k_p3_x, k_p3_y);
            pathFigure.Segments.Add(ls);
            ls = new LineSegment();
            ls.Point = new Point(k_p4_x, k_p4_y);
            pathFigure.Segments.Add(ls);
            ls = new LineSegment();
            ls.Point = new Point(k_p1_x, k_p1_y);
            pathFigure.Segments.Add(ls);

            // Add Inputs & outputs
            // D Input 
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_in_d_p1_x, k_in_d_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_in_d_p2_x, k_in_d_p1_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Clock Input
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_in_clk_p1_x, k_in_clk_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_in_clk_p2_x, k_in_clk_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Q Output 
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_out_q_p1_x, k_out_q_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_out_q_p2_x, k_out_q_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Q Not Output 
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_out_q_not_p1_x, k_out_q_not_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_out_q_not_p2_x, k_out_q_not_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Draw letters
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_d_p1_x, k_d_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_d_p2_x, k_d_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            ArcSegment ars = new ArcSegment();
            ars.Point = new Point(k_d_p3_x, k_d_p3_y);
            ars.Size = new Size(k_d_radius, k_d_radius);
            ars.RotationAngle = k_d_rotational_angle;
            ars.SweepDirection = SweepDirection.Counterclockwise;
            pathFigure.Segments.Add(ars);
            // Add "CLK"
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_clk_p1_x, k_clk_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_clk_p2_x, k_clk_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_clk_p3_x, k_clk_p3_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_clk_p4_x, k_clk_p4_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Add "Q"
            EllipseGeometry egQ = new EllipseGeometry();
            egQ.Center = new Point(k_q_center_x, k_q_center_y);
            egQ.RadiusX = k_q_radius;
            egQ.RadiusY = k_q_radius;
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_q_p1_x, k_q_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_q_p2_x, k_q_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Add "Q Not"
            EllipseGeometry egQNot = new EllipseGeometry();
            egQNot.Center = new Point(k_q_not_center_x, k_q_not_center_y);
            egQNot.RadiusX = k_q_not_radius;
            egQNot.RadiusY = k_q_not_radius;
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_q_not_p1_x, k_q_not_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_q_not_p2_x, k_q_not_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_q_not_p3_x, k_q_not_p3_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_q_not_p4_x, k_q_not_p4_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);

            // Setup the path
            path = new Path();
            path.Stretch = Stretch.Fill;
            path.StrokeLineJoin = PenLineJoin.Round;
            path.Stroke = new SolidColorBrush(strokeColor);
            path.Fill = new SolidColorBrush(backgroundColor);
            path.StrokeThickness = k_dbl_stroke_thickness;
            GeometryGroup gg = new GeometryGroup();
            gg.Children.Add(pathGeometry);
            gg.Children.Add(egQ);
            gg.Children.Add(egQNot);            
            path.Data = gg;
            path.MouseLeftButtonDown += new MouseButtonEventHandler(CustomMouseLeftButtonDown);
            path.MouseLeftButtonUp += new MouseButtonEventHandler(CustomMouseLeftButtonUp);
            path.MouseMove += new MouseEventHandler(CustomMouseMove);
            path.MouseEnter += new MouseEventHandler(CustomMouseEnter);
            path.MouseLeave += new MouseEventHandler(CustomMouseLeave);

            Children.Add(path);

            // Setup output points
            outputLeft.X = k_out_q_p1_x;
            outputLeft.Y = k_out_q_p1_y;
            outputRight.X = k_out_q_p2_x;
            outputRight.Y = k_out_q_p2_y;

            // Setup rubberband output line
            outputLine = new Line();
            outputLine.Stroke = new SolidColorBrush(rubberbandColor);
            outputLine.StrokeThickness = k_dbl_stroke_thickness;
            outputLine.Visibility = System.Windows.Visibility.Collapsed;

            Children.Add(outputLine);

            // Set the initial position
            path.SetValue(Canvas.LeftProperty, k_initial_left);
            path.SetValue(Canvas.TopProperty, k_initial_top);
            canvLeft = k_initial_left;
            canvTop = k_initial_top;
        }

        protected override int Connect(Device logicDevice, Point clickedPoint)
        {
            double yScale = 1;
            Panel parent = (Panel)Parent;
            if (Application.Current.Host.Content.IsFullScreen)
                yScale = Application.Current.Host.Content.ActualHeight / parent.ActualHeight;
            // If the point clicked was in the upper third of the gate, choose J input
            if ((clickedPoint.Y/yScale - canvTop) < (k_p1_y) / 2)
            {
                if (inputDevices[k_int_d_input] == null)
                {
                    inputDevices[k_int_d_input] = logicDevice;
                    return k_int_d_input;
                }
            }
            else // Try to connect to the CLK input
            {
                if (inputDevices[k_int_clk_input] == null)
                {
                    inputDevices[k_int_clk_input] = logicDevice;
                    return k_int_clk_input;
                }
            }
            // Can't connect...
            return k_int_not_connected;
        }

        protected override Point GetInputLocation(int inputNumber)
        {
            Point p = new Point();
            // Figure out the correct point information
            if (inputNumber == k_int_d_input)
            {
                p.X = canvLeft + k_in_d_p1_x + k_int_input_x_offset;
                p.Y = canvTop + k_in_d_p1_y + k_int_input_y_offset;
            }
            else if (inputNumber == k_int_clk_input)
            {
                p.X = canvLeft + k_in_clk_p1_x + k_int_input_x_offset;
                p.Y = canvTop + k_in_clk_p1_y + k_int_input_y_offset;
            }
            return p;
        }

        // Sets the state of the specified input to the given value
        public override void SetInputState(int inputNumber, bool state)
        {
            if (inputNumber == k_int_d_input)
                // Set the first input state
                inputDState = state;
            else if (inputNumber == k_int_clk_input)
                inputClkState = state;
            // Set the state when the clock is positive
            if (inputClkState && inputNumber == k_int_clk_input)
            {
                // Ensure that J and K inputs have been updated
                if (inputDevices[k_int_d_input] != null)
                    inputDevices[k_int_d_input].UpdateOutputState();
                // Set the output state - Prevent infinite feedback
                bool newOutputState = inputDState;
                if (newOutputState != outputState)
                {
                    outputState = newOutputState;
                    // Set the input state of any connected devices
                    UpdateOutputState();
                }
            }
        }

        protected override void CustomMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            beginX = e.GetPosition(this).X;
            beginY = e.GetPosition(this).Y;
            trackingMouseMove = true;
            moving = false;
            FrameworkElement fe = sender as FrameworkElement;
            canvLeft = Convert.ToDouble(fe.GetValue(Canvas.LeftProperty));
            canvTop = Convert.ToDouble(fe.GetValue(Canvas.TopProperty));
            // Reset the stroke color
            path.Stroke = strokeBrush;
            path.StrokeThickness = k_dbl_stroke_thickness;
            // Assume that this will be selected
            if (initiated)
                selected = !selected;

            if (fe != null)
            {
                fe.CaptureMouse();
            }

            // Reset z-orders
            Panel parentPanel = (Panel)Parent;
            int topMost = 0, zOrder;
            for (int i = 0; i < parentPanel.Children.Count; i++)
                if (parentPanel.Children[i] is Device)
                {
                    Device dev = (Device)parentPanel.Children[i];
                    // Determine the z-order of all item
                    zOrder = (int)dev.GetValue(Canvas.ZIndexProperty);
                    if (zOrder > topMost)
                        topMost = zOrder;
                }
            // Set this above the rest
            SetValue(Canvas.ZIndexProperty, topMost + 1);

            // Get the point that was clicked
            clickedPoint.X = beginX - canvLeft;
            clickedPoint.Y = beginY - canvTop;

            // Determine if output has been clicked
            if ((outputLine != null) && initiated)
                if (clickedPoint.X > outputLeft.X)
                {
                    if (clickedPoint.Y < (k_p1_y / 2))
                        currOutputNumber = 0;
                    else
                        currOutputNumber = 1;
                    Point outputPoint = GetOutputLocation();
                    beginX = outputPoint.X;
                    beginY = outputPoint.Y;
                    outputLine.X1 = beginX;
                    outputLine.Y1 = beginY;
                    outputLine.X2 = beginX;
                    outputLine.Y2 = beginY;
                    outputLine.Visibility = System.Windows.Visibility.Visible;
                }
            // Note that the mouse event has been handled
            e.Handled = true;
        }

        // Returns the output location of the device
        public override Point GetOutputLocation()
        {
            if (currOutputNumber == 0)
                outputRight.Y = k_out_q_p2_y;
            else
                outputRight.Y = k_out_q_not_p2_y;
            return new Point(outputRight.X + canvLeft, outputRight.Y + canvTop + 1);
        }

        // Updates the state of all connected output devices
        public override void UpdateOutputState()
        {
            foreach (OutputDevice od in outputDevices)
                if (od.outputNumber == 0)
                    od.device.SetInputState(od.inputNumber, outputState);
                else
                    od.device.SetInputState(od.inputNumber, !outputState);
        }

    }
}
